﻿package net.wangit.jdbc.transaction;

import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

import net.wangit.jdbc.CacheSynchronization;
import net.wangit.jdbc.DB;
import net.wangit.jdbc.JDBCException;
import net.wangit.util.Checker;
import net.wangit.util.NamingUtil;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public abstract class JTATransaction implements TransactionImplementor {

	private static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
	private UserTransaction ut;
	private boolean newTransaction;
	private boolean begun;
	private boolean commitFailed;
	private InitialContext context;
	private String utName;
	private TransactionManager transactionManager;
	private static final Log log = LogFactory
			.getLog(net.wangit.jdbc.transaction.JTATransaction.class);

	public JTATransaction() {
	}

	public void commit() throws JDBCException {
		if (!begun)
			throw new TransactionException(
					"Transaction not successfully started");
		if (log.isDebugEnabled())
			log.debug("commit");
		if (newTransaction)
			try {
				ut.commit();
			} catch (Exception e) {
				commitFailed = true;
				log.error("Commit failed", e);
				throw new TransactionException(
						"Commit failed with exception: ", e);
			}
	}

	public void rollback() throws JDBCException {
		if (!begun)
			throw new TransactionException(
					"Transaction not successfully started");
		if (log.isDebugEnabled())
			log.debug("rollback");
		try {
			if (newTransaction) {
				if (!commitFailed)
					ut.rollback();
			} else {
				ut.setRollbackOnly();
			}
		} catch (Exception e) {
			log.error("Rollback failed", e);
			throw new TransactionException("Rollback failed with exception", e);
		}
	}

	public net.wangit.jdbc.Transaction begin(DB db)
			throws TransactionException {
		if (log.isDebugEnabled())
			log
					.debug((new StringBuilder(
							"Looking for UserTransaction under: ")).append(
							utName).toString());
		try {
			ut = (UserTransaction) context.lookup(utName);
		} catch (NamingException ne) {
			log.error("Could not find UserTransaction in JNDI", ne);
			throw new TransactionException(
					"Could not find UserTransaction in JNDI: ", ne);
		}
		if (ut == null)
			throw new TransactionException(
					"A naming service lookup returned null");
		if (log.isDebugEnabled())
			log.debug("Obtained UserTransaction");
		try {
			newTransaction = ut.getStatus() == 6;
			if (newTransaction) {
				if (log.isDebugEnabled())
					log.debug("beginning new transaction");
				ut.begin();
			}
		} catch (Exception e) {
			log.error("Begin failed", e);
			throw new TransactionException("Begin failed with exception", e);
		}
		if (!newTransaction && transactionManager != null)
			try {
				transactionManager.getTransaction().registerSynchronization(
						new CacheSynchronization());
			} catch (Exception se) {
				log.error("Could not register Synchronization", se);
				throw new TransactionException(
						"Could not register Synchronization", se);
			}
		begun = true;
		return this;
	}

	public void configure(Properties props, String userTransaction)
			throws TransactionException {
		try {
			context = NamingUtil.getInitialContext(props);
		} catch (NamingException ne) {
			log.error("Could not obtain initial context", ne);
			throw new TransactionException("Could not obtain initial context",
					ne);
		}
		transactionManager = getTransactionManager(context);
		if (!Checker.isEmpty(userTransaction)) {
			utName = userTransaction;
		} else {
			utName = getUserTransactionName();
			if (Checker.isEmpty(utName))
				utName = JTATransaction.DEFAULT_USER_TRANSACTION_NAME;
		}
	}

	public abstract TransactionManager getTransactionManager(
			InitialContext initialcontext) throws TransactionException;

	public abstract String getUserTransactionName();

}
